// (c) S.A. Velastin 2007
/*
        Version 3.2 (c.f. Version 3.1 here we only serve a single stream)
        A program that uses the REASON Architecture framework to demonstrate a video server
        Syntax
                VideoServer factory_name object_name type video_file
                
                factory_name:   Name of the server factory that will serve these objects
                object_name:    the name for the object associated with a video stream
                type:           channel, pool or signal determines what type of IDA to use (recommended: signal)
                video_file:     Name of the video file associated with the video stream
                
*/
#include <tao/corba.h>
#include <iostream>
#include <fstream>
#include <string>
#include <list>

// Corba stuff
#include "orbsvcs/CosNamingC.h"
#include "CommunicationC.h"

// Opencv stuff
#include <cv.h>
#include <highgui.h>

#include "common.h"

using namespace std;

//--------------------------------------------------------------------------------------------
// Gets a reference to the Name Service
CosNaming::NamingContext_var GetNamingContext (CORBA::ORB_var orb) {
  CosNaming::NamingContext_var naming_context = NULL;
  cout << "Trying to locate the Naming Service\n";
  try {
    CORBA::Object_var naming_context_object = orb->resolve_initial_references ("NameService");
    naming_context = CosNaming::NamingContext::_narrow (naming_context_object.in ());
    if (CORBA::is_nil(naming_context))
      cout << "Could not resolve Name Service\n";
    else
      cout << "Got Name Service\n";
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting Name Service: " << e << endl;
  }
  return naming_context;
}

//--------------------------------------------------------------------------------------------
// Gets a reference to an IDA Factory via Naming Service
TIDAFactory_var GetIDAFactory (char *FactoryName, CosNaming::NamingContext_var naming_context) {
  TIDAFactory_var Factory = NULL;
  cout << "Trying to get reference to factory object '" << FactoryName << "'\n";
  // Create and initialise the name sequence
  CosNaming::Name name (1);
  name.length(1);
  // Initialise the name
  name[0].id = CORBA::string_dup (FactoryName);

  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) 
      cerr << "Unable to resolve name\n";
    else {
        // Get a reference to the Factory object
      Factory = TIDAFactory::_narrow(obj);
      if (CORBA::is_nil(Factory))
        cerr << "Unable to get Factory reference\n";
      return Factory;
    }
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting factory: " << e << endl;
  }
  return Factory;
}


//--------------------------------------------------------------------------------------------
// Gets a reference to an object by getting it from the Name Service
TIDA_var GetIDAReference (char *objname, CosNaming::NamingContext_var naming_context) {
  TIDA_var result=NULL;
  CosNaming::Name name (1);
  name.length(1);
  name[0].id = CORBA::string_dup (objname);
  cout << "Client looking for IDA name '" << objname << "' in name service\n";
  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) {
    cerr << "Unable to resolve name\n";
    return NULL;
    }
    // The name was resolved, try to use the object
    result = TIDA::_narrow (obj);
    if (CORBA::is_nil(result))
      cerr << "Unable to get the Factory's object reference\n";
  }
  catch (CORBA::Exception &e) {
    cerr << "Exception when getting IDA object reference: " << e << endl;
  }
  return result;  
}

//------------------------------------------------------------------------------
// Fills up an IplImage from a TImage
// Assumed that Image owns its pixel data so we create a new array to be owned by CVImage
// NB: Caller has responsibility for freeing memory in CVImage
bool FillOpenCVData (IplImage *CVImage, const TImage *Image)
{ if (Image->imageSize != Image->ImagePixels.length()) {
  cerr << "FillOpenCVData: inconsistent image size " << Image->imageSize << " and length " 
      << Image->ImagePixels.length() << "\n";
  return false;
}
  
  CVImage->nSize = sizeof(IplImage);
  CVImage->ID = 0;                      // version (=0)
  CVImage->nChannels = Image->nChannels;// Most of OpenCV functions support 1,2,3 or 4 channels 
  CVImage->alphaChannel = 0;            // ignored by OpenCV 
  CVImage->depth = Image->depth;        // pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
                                        //IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported
  //    char colorModel[4];           ignored by OpenCV 
  //    char channelSeq[4];           ditto 
  CVImage->dataOrder = 0;               // 0 - interleaved color channels, 1 - separate color channels.
                                        // cvCreateImage can only create interleaved images 
  CVImage->origin = Image->origin;      // 0 - top-left origin,
                                        // 1 - bottom-left origin (Windows bitmaps style) 
  // int  align;                           Alignment of image rows (4 or 8).
                                        // OpenCV ignores it and uses widthStep instead 
  CVImage->width = Image->width;        // image width in pixels 
  CVImage->height = Image->height;      // image height in pixels 
  CVImage->roi = NULL;                  // image ROI. when it is not NULL, this specifies image region to process 
  CVImage->maskROI = NULL;              // must be NULL in OpenCV 
  CVImage->imageId = NULL;              // ditto
  CVImage->tileInfo = NULL;             // ditto
  CVImage->imageSize = Image->imageSize;// image data size in bytes
                                        // (=image->height*image->widthStep
                                        //  in case of interleaved data)
  CVImage->widthStep = Image->widthStep;// size of aligned image row in bytes
  //       int  BorderMode[4];           border completion mode, ignored by OpenCV 
  //       int  BorderConst[4];          ditto
  char *Pixels = new (nothrow) char[Image->imageSize];
  if (!Pixels) {
    cerr << "FillOpenCVData: could not create pixels array\n";
    return false;
  }
  memcpy (Pixels, Image->ImagePixels.get_buffer(), Image->imageSize);
  
  CVImage->imageData = Pixels;          // pointer to aligned image data
  CVImage->imageDataOrigin = Pixels;    // pointer to a very origin of image data
                                        // (not necessarily aligned) -
                                        // it is needed for correct image deallocation
  // note that the caller is now responsible for releasing Pixels
  return true;
}

//----------------------------------------------------------------------------------
// Fills a TImage from IplImage
// A new array of pixels is created that is then managed by Image
bool FillImageData (TImage *Image, const IplImage *CVImage)
{ Image->nChannels = CVImage->nChannels;
  Image->depth = CVImage->depth;
  Image->origin = CVImage->origin;
  Image->width = CVImage->width;
  Image->height = CVImage->height;
  Image->imageSize = (CORBA::Long) CVImage->imageSize;
  Image->widthStep = CVImage->widthStep;
  // Now copy the pixel data by creating memory and copying
  CORBA::Octet *ImageData;
  ImageData = new (nothrow) CORBA::Octet[CVImage->imageSize];
  if (!ImageData) {
    cerr << "FillImageData failed to create temporary memory\n";
    return false;
  }
  memcpy (ImageData, CVImage->imageDataOrigin, CVImage->imageSize);  // copy all pixels
  
  // Important: Image now manages ImageData, so do NOT delete it
  // replace() should deal with freeing previous data
  Image->ImagePixels.replace (CVImage->imageSize, CVImage->imageSize, ImageData);
  cout << "Pixel data copied, size: " << CVImage->imageSize << " " << Image->imageSize << 
      " " << Image->ImagePixels.length() << "\n";
  return true;
}


// Possible exit codes
#define EXIT_NORMAL             0
#define EXIT_WRONGPARAMETERS    1
#define EXIT_NONAMINGSERVICE    2
#define EXIT_NOFACTORY          3
#define EXIT_NOIMAGEMEMORY      4
#define EXIT_NOIMAGEFILE        5
#define EXIT_NOIDATYPE          6
#define EXIT_NOVIDEOFILE        7
#define EXIT_NOOBJECTCREATE     8
#define EXIT_NOOBJECTGET        9
#define EXIT_CONVERSION         10

// Here for each object we create we store sufficient information to handle it
typedef struct {
  TIDA_var      IDA_obj;                     // so we can use it later 
  TIDAType      IDAType;
  char          ObjName[MAXIDANAMELENGTH];   // and the name
  bool          stopped;
  IplImage     *frame;
  int           frame_number;
} TVideoElem; 
typedef list<TVideoElem> TVideoList;        // This defines a list of objects

// TODO: Check memory allocation to trap orphan memory

//--------------------------------------------------------------------------------------------
int main(int argc, char* argv[]) { 

  try { 
    CosNaming::NamingContext_var naming_context;
    TIDAFactory_var Factory;                    // Reference to an IDA Factory
    TVideoList  VideoList;
    int         NFrame=0;
    TIDAType    IDAType;
    CvCapture   *CVvideo;
    IplImage    *CVframe;
    CORBA::Any  SentImage;
    TImage      Image;
    TIDA_var    IDA_obj;                     // reeference to the IDA
    
    // Process & remove any CORBA arguments in the command line
    CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
    // Check command line arguments.
    if (argc < 5) { 
      cerr << "Usage: VideoServer factory_name object_name type video_file\n";
      cerr << "type := channel | pool | signal\n";
      exit (EXIT_WRONGPARAMETERS); }
    
    // Get a reference to the naming service
    naming_context = GetNamingContext (orb);
    if (CORBA::is_nil(naming_context))
      exit (EXIT_NONAMINGSERVICE);
    
    // Get a reference to the factory object
    Factory = GetIDAFactory (argv[1], naming_context);
    if (CORBA::is_nil (Factory))  
      exit (EXIT_NOFACTORY);
    
    // Object name
    cout << "Object '" << argv[2] << "'\n";           // we do endline to flush
    // Decode IDA type
    { 
      bool found_type = false;
      if (!strcmp("channel", argv[3])) {
        cout << "Channel\n"; IDAType = IDAchannel; found_type = true;
      }
      if (!strcmp("pool", argv[3])) {
        cout << "Pool\n"; IDAType = IDApool; found_type = true;
      }
      if (!strcmp("signal", argv[3])) {
        cout << "Signal\n"; IDAType = IDAsignal; found_type = true;
      }
      if (!found_type) {
        cerr << "Unknown IDA type" << argv[3] << "\n";
        exit (EXIT_NOIDATYPE);
      }
    }
    // Video file
    CVvideo = cvCaptureFromFile (argv[4]);
    if (!CVvideo) {
      cerr << "Unable to open video file '" << argv[4] << "'\n";
      exit (EXIT_NOVIDEOFILE);
    }
    cout << "Streaming video file '" << argv[4] << "'\n";
    SentImage <<= Image;                                // this does not matter as not a flash
    // Create and retrieve object
    if (!Factory->CreateObject (argv[2], IDAType, SentImage)) {
      cerr << "Unable to create object\n";
      exit (EXIT_NOOBJECTCREATE);
    }
    IDA_obj = GetIDAReference (argv[2], naming_context);
    if (CORBA::is_nil(IDA_obj)) {
      cerr << "Unable to retrieve reference to object\n";
      exit (EXIT_NOOBJECTGET);
    }

      // So far so good, now we try to read a frame
    CVframe = cvQueryFrame(CVvideo);            // get an image from the video
    if (!FillImageData(&Image, CVframe)) {
      cerr << "Error converting Image to IplImage\n";
      Factory->RemoveObject (argv[2]);
      exit (EXIT_CONVERSION);
    }
    SentImage <<= Image;                    // copy to type any
      
    while (CVframe) {
      if (!IDA_obj->PutData (SentImage, 10000))
        cout << ".";
      else {
        cout << "[" << NFrame++ << "]\n";
        CVframe = cvQueryFrame (CVvideo);
        if (!FillImageData(&Image, CVframe)) {
          cerr << "Error converting Image to IplImage\n";
          Factory->RemoveObject (argv[2]);
          exit (EXIT_CONVERSION);
        }
        // Convert the IplImage to TImage
        SentImage <<= Image;                    // copy to type any
      }
    } // there is still video
        
    cout << "Stream completed\n";
    Factory->RemoveObject (argv[2]);
  }
  catch (CORBA::Exception& e) {
    cerr << "CORBA Exception: " << e << endl;
  }
  cout << "Exiting ...\n";
  return EXIT_NORMAL; 
}
